﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SmartMathLibrary.NonlinearEquationSolvers;

namespace SmartMathLibrary
{
    /// <summary>
    /// This class provides the factoricion of a polynomial into polynomials with a maximum of 
    /// three degrees.  
    /// </summary>
    [Serializable]
    public class PolynomialFactorizer
    {
        /// <summary>
        /// The polynomial which should be factorize.
        /// </summary>
        private SimplePolynomial polynomial;

        /// <summary>
        /// Initializes a new instance of the <see cref="PolynomialFactorizer"/> class.
        /// </summary>
        /// <param name="polynomial">The polynomial which should be factorize.</param>
        public PolynomialFactorizer(SimplePolynomial polynomial)
        {
            if (polynomial == (SimplePolynomial) null)
            {
                throw new ArgumentNullException("polynomial");
            }

            this.polynomial = polynomial;
        }

        /// <summary>
        /// Gets or sets the polynomial which should be factorize.
        /// </summary>
        /// <value>The polynomial which should be factorize.</value>
        public SimplePolynomial Polynomial
        {
            get { return polynomial; }
            set { polynomial = value; }
        }

        /// <summary>
        /// Factorizes the specified polynomial into polynomials with a maximum of three degrees.
        /// </summary>
        /// <returns>The factors of the factorized polynomial.</returns>
        public SimplePolynomial[] FactorizePolynomial()
        {
            if (this.polynomial.Degree < 4)
            {
                return new[] {this.polynomial};
            }

            BairstowComplexRootFinder rootFinder = new BairstowComplexRootFinder(this.polynomial);
            List<SimplePolynomial> result = new List<SimplePolynomial>();
            ComplexNumber[] tempuri = rootFinder.FindRoots();

            for (int j = 0; j < tempuri.Length; j++)
            {
                tempuri[j] *= -1;
            }

            int i = 0;

            while (i < tempuri.Length)
            {
                if ((tempuri[i].RealNumberPart != 0) && (tempuri[i].ImaginaryNumberPart != 0))
                {
                    ComplexNumber constantValue = tempuri[i] * tempuri[i + 1];

                    result.Add(new SimplePolynomial(constantValue.RealNumberPart, 2 * tempuri[i].RealNumberPart, 1));
                    i++;
                }

                i++;
            }

            double[] realRoots = PolynomialFactorizer.ExtractRealRoots(tempuri);
            int end = realRoots.Length;

            if (realRoots.Length % 2 != 0)
            {
                end = realRoots.Length - 1;
            }

            for (int j = 0; j < end; j += 2)
            {
                result.Add(new SimplePolynomial(realRoots[j] * realRoots[j + 1], realRoots[j] + realRoots[j + 1], 1));
            }

            if (realRoots.Length % 2 != 0)
            {
                result[result.Count - 1] *= new SimplePolynomial(realRoots[realRoots.Length - 1], 1);
            }

            double x = 0;
            double y = this.polynomial.SolveAt(x);

            while (y == 0)
            {
                x += 1;
                y = this.polynomial.SolveAt(x);
            }

            double partResult = 1;

            for (int j = 0; j < result.Count; j++)
            {
                partResult *= result[j].SolveAt(x);
            }

            result.Add(new SimplePolynomial(y / partResult));

            return result.ToArray();
        }

        /// <summary>
        /// Extracts all real roots of the specified array.
        /// </summary>
        /// <param name="roots">The array of roots to extract.</param>
        /// <returns>The extracted real roots.</returns>
        private static double[] ExtractRealRoots(ComplexNumber[] roots)
        {
            List<double> result = new List<double>();

            for (int i = 0; i < roots.Length; i++)
            {
                if (roots[i].ImaginaryNumberPart == 0)
                {
                    result.Add(roots[i].RealNumberPart);
                }
            }

            return result.ToArray();
        }
    }
}